﻿using DevExpress.XtraGrid;
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;
using ValidationEngine.Commons;
using ValidationEngine.Commons.Enums;
using ValidationEngine.Tools;

namespace ValidationEngine.Core
{
    /// <summary>
    /// IModelMgr implementation
    /// </summary>
    internal class ModelMgr : IModelMgr
    {
        #region Members
        private IValidateableModel _model;
        private readonly ValidationProvider _validationProvider;
        private bool _validationEnabled = true;
        #endregion

        #region Ctor
        internal ModelMgr(ValidationProvider validationProvider)
        {
            _validationProvider = validationProvider;
        } 
        #endregion

        #region Tools
        /// <summary>
        /// Scans model and subscribes to it's events to validate it.
        /// </summary>
        /// <param name="model">Model instance</param>
        private void SubscribeToModelEvents(IValidateableModel model)
        {
            if (null == model)
            {
                throw new EngineCoreExcception("model should not be null");
            }
            Type modelType = model.GetType();
            _validationProvider.ValidatorsMgr.FindAndRegisterValidatorForType(modelType);

            model.PropertyValidating += model_PropertyValidating;
            foreach (PropertyInfo property in modelType.GetProperties(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly))
            {
                if (typeof(IValidateableModel).IsAssignableFrom(property.PropertyType))
                {
                    var propVal = property.GetValue(model, null) as IValidateableModel;
                    if (null != propVal)
                    {
                        SubscribeToModelEvents(propVal);
                    }
                }
                else if (property.PropertyType.IsGenericType && property.PropertyType.GetGenericTypeDefinition() == typeof(BindingList<>))
                {
                    IEnumerable list = (IEnumerable)property.GetValue(model, null);
                    if (typeof(IValidateableModel).IsAssignableFrom(list.AsQueryable().ElementType))
                    {
                        ((IBindingList)list).ListChanged += (sender, e) =>
                        {
                            IBindingList bindingList = (IBindingList)sender;
                            if (e.ListChangedType == ListChangedType.ItemAdded)
                            {
                                SubscribeToModelEvents((IValidateableModel)bindingList[e.NewIndex]);
                            }
                        };
                        foreach (IValidateableModel item in list)
                        {
                            SubscribeToModelEvents(item);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// PropertyValidating event handler
        /// </summary>
        /// <param name="e">Event arguments</param>
        private void model_PropertyValidating(PropertyValidationEventArgs e)
        {
            IEnumerable<Control> ctrls = _validationProvider.ViewMgr.GetControlsByModelProperty(e.Context.Model.GetType(), e.Context.PropertyName);
            IValidator validator = _validationProvider.ValidatorsMgr.GetValidatorForType(e.Context.Model.GetType());
            if (null != validator)
            {
                e.ValidationInfo = validator.Validate(e.Context);
                foreach (Control ctrl in ctrls)
                {
                    if (ctrl is GridControl || ctrl is DataGridView)
                    {
                        BindingSource source = _validationProvider.ViewMgr.GetSourceByControl(ctrl);
                        source.ResetBindings(false);
                    }
                    _validationProvider.ViewMgr.SetValidationInfoForControl(ctrl, e.ValidationInfo);
                }
                foreach (var relativeInfo in e.ValidationInfo.RelativesInfo)
                {
                    foreach (var ctrl in _validationProvider.ViewMgr.GetControlsByModelProperty(e.Context.Model.GetType(), relativeInfo.Key))
                    {
                        _validationProvider.ViewMgr.SetValidationInfoForControl(ctrl, e.ValidationInfo.RelativesInfo[relativeInfo.Key]);
                    }
                }
            }
        } 
        #endregion

        #region IModelMgr
        /// <summary>
        /// Validates full model
        /// </summary>
        /// <param name="ctx"></param>
        public void RevalidateModel(RuleApplingContext ctx)
        {
            Model.ReValidate(ctx);
            //_validationEnabled = false;
            //_validationMgr.ViewMgr.ResetAllBindings();
            //_validationEnabled = true;
        }

        public IValidateableModel Model
        {
            get 
            { 
                if (null == _model)
                {
                   throw new EngineCoreExcception("Model is not set.");
                }
                return _model; 
            }
            set
            {
                if (null == value)
                {
                    throw new ArgumentNullException("model");
                }
                _model = value;
                SubscribeToModelEvents(_model);
            }
        }

        public BindingSource ModelSource
        {
            set
            {
                if (null != value)
                {
                    value.CurrentChanged += modelSource_CurrentChanged;
                }
            }
        }

        void modelSource_CurrentChanged(object sender, EventArgs e)
        {
            if (!_validationEnabled)
            {
                return;
            }

            BindingSource bs = sender as BindingSource;
            if (null != bs && null != bs.Current)
            {
                IValidateableModel model = bs.Current as IValidateableModel;

                if (null != model && !Object.ReferenceEquals(_model, model))
                {
                    Model = model;
                    RevalidateModel(RuleApplingContext.Always);
                }
            }
        }
        #endregion
    }
}
